package com.tinywind.admin.common.config.shiro;

import com.tinywind.admin.common.consts.SystemConst;
import com.tinywind.admin.common.utils.RedisUtil;
import com.tinywind.admin.module.system.entity.SysUserInfo;
import com.tinywind.admin.module.system.service.LoginService;
import com.tinywind.admin.module.system.service.SysRolePermissionService;
import com.tinywind.admin.module.system.service.SysUserRoleService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.apache.shiro.util.ByteSource;
import org.crazycake.shiro.RedisSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

/**
 * @author liuxingyu01
 * @date 2021-01-07-22:11
 * @description shiro自定义Relam
 **/
public class LoginRelam extends AuthorizingRealm {
    final static Logger logger = LoggerFactory.getLogger(LoginRelam.class);

    @Autowired
    private LoginService loginService;

    @Autowired
    private SysUserRoleService sysUserRoleService;

    @Autowired
    private SysRolePermissionService sysRolePermissionService;

    @Autowired
    private RedisSessionDAO redisSessionDAO;

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 身份认证
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 获取基于用户名和密码的令牌：实际上这个authcToken是从LoginController里面currentUser.login(token)传过来的
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        // 根据用户名查找到用户信息
        SysUserInfo userInfo = loginService.getUserInfo(token.getUsername());
        // 没找到帐号(用户不存在)
        if (userInfo == null) {
            throw new UnknownAccountException();
        }
        // 校验用户状态(用户已失效)
        if ("1".equals(userInfo.getStatus())) {
            throw new DisabledAccountException();
        }

        // 密码错误五次，则锁定账号1800s
        int errorTimes = redisUtil.get(SystemConst.SYSTEM_LOGIN_TIMES + ":" + userInfo.getAccount()) == null ? 0
                : Integer.parseInt((String) redisUtil.get(SystemConst.SYSTEM_LOGIN_TIMES + ":" + userInfo.getAccount()));
        if (errorTimes >= 5) {
            throw new ExcessiveAttemptsException();
        }

        /*
         * 第一个参数：传入的都是com.java.entity包下的User类的user对象
         * 第二个参数:  传入的是从数据库中获取到的password，然后再与token中的password进行对比，匹配上了就通过，匹配不上就报异常
         * 第三个参数，盐–用于加密密码对比。 若不需要，则可以设置为空 “”
         * 第四个参数：当前realm的名字
         */
        // 认证缓存信息
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                userInfo, // 用户信息
                userInfo.getPassword(), // 用户密码
                ByteSource.Util.bytes(SaltConfig.getSalt()), // 设置盐值
                getName());

        return authenticationInfo;
    }


    /**
     * 角色权限认证
     *
     * @param principalCollection
     * @return AuthorizationInfo
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SysUserInfo authorizingUser = (SysUserInfo) principalCollection.getPrimaryPrincipal();
        if (null != authorizingUser) {
            // 权限信息对象info,用来存放查出的用户的所有的角色（role）及权限（permission）
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            // 获得用户角色列表
            simpleAuthorizationInfo.setRoles(sysUserRoleService.listUserRoleByUserId(authorizingUser.getUserId()));
            // 获得权限列表
            simpleAuthorizationInfo.setStringPermissions(sysRolePermissionService.listRolePermissionByUserId(authorizingUser.getUserId()));
            return simpleAuthorizationInfo;
        }
        return null;
    }


    /**
     * 根据用户account清除认证信息
     *
     * @param accounts 这个好像不起作用
     */
    public void clearCachedAuthenInfo(List<String> accounts) {
        if (null == accounts || accounts.size() == 0) {
            return;
        }
        List<SimplePrincipalCollection> list = getSpcListByUserIds(accounts);
        for (SimplePrincipalCollection simplePrincipalCollection : list) {
            this.clearCachedAuthenticationInfo(simplePrincipalCollection);
        }
    }


    /**
     * 清理当前登陆用户权限缓存
     */
    public void clearCachedAuthorInfo() {
        this.clearCachedAuthorizationInfo(SecurityUtils.getSubject().getPrincipals());
    }


    /**
     * 根据用户account清除当前session存在的用户的权限缓存
     *
     * @param accounts
     * @description 经测试，这个是有效的
     */
    public void clearCachedAuthorInfoByAccounts(List<String> accounts) {
        if (null == accounts || accounts.size() == 0) {
            return;
        }
        List<SimplePrincipalCollection> list = getSpcListByUserIds(accounts);
        for (SimplePrincipalCollection simplePrincipalCollection : list) {
            this.clearCachedAuthorizationInfo(simplePrincipalCollection);
        }
    }


    /**
     * 根据用户id获取所有spc
     *
     * @param accounts
     */
    private List<SimplePrincipalCollection> getSpcListByUserIds(List<String> accounts) {
        // 获取所有session
        Collection<Session> sessions = redisSessionDAO.getActiveSessions();
        // 定义返回
        List<SimplePrincipalCollection> list = new ArrayList<SimplePrincipalCollection>();
        for (Session session : sessions) {
            // 获取session登录信息。
            Object obj = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
            if (null != obj && obj instanceof SimplePrincipalCollection) {
                // 强转
                SimplePrincipalCollection spc = (SimplePrincipalCollection) obj;
                // 判断用户，匹配用户ID。
                obj = spc.getPrimaryPrincipal();
                if (null != obj && obj instanceof SysUserInfo) {
                    SysUserInfo sysUserInfo = (SysUserInfo) obj;
                    logger.info("SingletonLoginUtils -- getSpcListByUserIds -- user: {}", sysUserInfo);
                    // 比较用户ID，符合即加入集合
                    if (null != sysUserInfo && accounts.contains(sysUserInfo.getAccount())) {
                        list.add(spc);
                    }
                }
            }
        }
        return list;
    }


    /**
     * 根据account删除用户登陆信息，使他强制下线
     *
     * @param account
     */
    public void forcedOffline(String account) {
        // 从缓存中获取Session
        Session session = null;
        Collection<Session> sessions = redisSessionDAO.getActiveSessions();
        SysUserInfo sysUserInfo;
        Object attribute = null;
        for (Session sessionInfo : sessions) {
            // 遍历Session,找到该用户名称对应的Session
            attribute = sessionInfo.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
            if (attribute == null) {
                continue;
            }
            sysUserInfo = (SysUserInfo) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal();
            if (sysUserInfo == null) {
                continue;
            }
            if (Objects.equals(sysUserInfo.getAccount(), account)) {
                session = sessionInfo;
                break;
            }
        }
        if (session == null || attribute == null) {
            return;
        }
        // 删除session
        redisSessionDAO.delete(session);
    }


}

